feat: add ViewModel progress infrastructure [DEV]#20729
feat: add ViewModel progress infrastructure [DEV]#20729criticalAY wants to merge 1 commit intoankidroid:mainfrom
Conversation
b6fabdb to
c278581
Compare
david-allison
left a comment
There was a problem hiding this comment.
This looks exceptional. One question on the race condition
c278581 to
d97aee6
Compare
d97aee6 to
97cb3d7
Compare
| /** The cancel callback for the currently active cancellable operation, if any */ | ||
| @Volatile | ||
| private var currentOnCancel: (() -> Unit)? = null |
There was a problem hiding this comment.
I feel we should store each onCancel.
This would require arbitrary access to the list of currently active methods, as they are removed based on the completion time.
| val mutex = Mutex() | ||
| var showJob: Job? = null | ||
| var dialogVisible = false | ||
| viewModel.progressManager.progress.collect { state -> |
There was a problem hiding this comment.
Consider using launchCollectionInLifecycleScope
| repeatOnLifecycle(Lifecycle.State.STARTED) { | ||
| val mutex = Mutex() | ||
| var showJob: Job? = null | ||
| var dialogVisible = false |
There was a problem hiding this comment.
Sorry... I was incorrect here. You need a lock when updating the progress state, but this isn't an update method.
There was a problem hiding this comment.
dialogVisible doesn't restore state from the FragmentManager
| activeCount.incrementAndGet() | ||
| if (onCancel != null) { | ||
| currentOnCancel = onCancel | ||
| } | ||
| updateState(message = message, amount = null, cancellable = onCancel != null) | ||
| try { | ||
| return ProgressScope(this).block() | ||
| } finally { | ||
| if (onCancel != null) { | ||
| currentOnCancel = null | ||
| } | ||
| if (activeCount.decrementAndGet() == 0) { | ||
| progress.value = ViewModelProgress.Idle | ||
| } | ||
| } |
There was a problem hiding this comment.
Concurrency bugs here: There's dependencies between currentOnCancel, activeCount and progress, but locks aren't held to serialise the mutations.
|
Thinking more: What's does progress/cancellation mean if there are two concurrent progress operations. Let's define this explicitly, both the UI and UX. |
416c3a0 to
79d16a6
Compare
| data object Idle : ViewModelProgress | ||
|
|
||
| /** | ||
| * One or more operations in progress. |
There was a problem hiding this comment.
The doc in the interface, message param and cancellable param say operation (singular), while this says One or more (plural). What's the actual supported behavior? Update that on the docs
| * `StateFlow` re-emissions on re-resume don't re-run [block] unnecessarily. | ||
| */ | ||
| context(activity: AnkiActivity) | ||
| fun <T> Flow<T>.launchLatestCollectionInLifecycleScope(block: suspend (T) -> Unit) { |
There was a problem hiding this comment.
I don't like the method as a general util method, although it's quite similar to some methods above it.
Name is too big, the manual value skipping can be unexpected, and doing that many things inside a single method can lead to bugs.
| viewModel: HasProgress, | ||
| delayMillis: Duration = 600.milliseconds, | ||
| ) { | ||
| (requireActivity() as AnkiActivity).observeProgress(viewModel, delayMillis) |
There was a problem hiding this comment.
Can throw a ClassCastException. I guess that none of the other methods actually need an AnkiActivity and could use a FragmentActivity instead. Either that or handle the throw
There was a problem hiding this comment.
That would require change in LoadingDialogFragment leaving a TODO for follow ups
| private fun formatProgressMessage(state: ViewModelProgress.Active): String? { | ||
| val amount = state.amount | ||
| if (amount != null && state.message != null) { | ||
| return "${state.message} ${amount.current}/${amount.max}" | ||
| } | ||
| return state.message | ||
| } |
There was a problem hiding this comment.
it would be nice to be able to add a custom format instead of being restricted to "${state.message} ${amount.current}/${amount.max}"
There was a problem hiding this comment.
Ref:
Anki-Android/AnkiDroid/src/main/java/com/ichi2/anki/CoroutineHelpers.kt
Lines 543 to 555 in d529ff1
Introduce a common pattern for progress notifications in ViewModels, decoupling progress dialog management from Activity/Fragment context.
79d16a6 to
3656563
Compare
Purpose / Description
Introduce a common pattern for progress notifications in ViewModels, decoupling progress dialog management from Activity/Fragment context.
Fixes
withProgressin ViewModels #19460Approach
How Has This Been Tested?
Unit test
Learning (optional, can help others)
NA
Checklist
Please, go through these checks before submitting the PR.